﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using BGSpaceShooterServer;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace BGSpaceShooter
{
    public class Server
    {

        private UdpClient udpClient;
        private int serverPort;
        private IPAddress serverAddress;

        public Server(int port, IPAddress ipAddress)
        {
            udpClient = new UdpClient();
            serverPort = port;
            serverAddress = ipAddress;
        }

        private void Send(ClientToServerPacket clientToServerPacket)
        {
            int rawsize = Marshal.SizeOf(clientToServerPacket);
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.StructureToPtr(clientToServerPacket, buffer, false);
            byte[] rawdatas = new byte[rawsize];
            Marshal.Copy(buffer, rawdatas, 0, rawsize);
            Marshal.FreeHGlobal(buffer);

            udpClient.Send(rawdatas, rawsize, new IPEndPoint(serverAddress, serverPort));
        }
        /// <summary>
        /// Prosi o przylaczenie
        /// </summary>
        /// <param name="userType"></param>
        /// <returns>playerId || -1 on deny || -2 - host not found</returns>
        public int RequestJoin(UserType userType = UserType.Player)
        {
            ClientToServerPacket clientToServerPacket = new ClientToServerPacket();
            clientToServerPacket.MessageType = MessageType.RequestJoin;
            clientToServerPacket.PlayerType = userType;
            clientToServerPacket.FighterType = GameObjectName.Viper;
            Send(clientToServerPacket);
            ServerToClientPacket packet;
            do
            {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);

                byte[] data;

                try
                {
                    data = udpClient.Receive(ref remote);
                }
                catch (Exception)
                {
                    return -2;
                }

                GCHandle pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
                packet =
                    (ServerToClientPacket)
                    Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), typeof(ServerToClientPacket));
                pinnedPacket.Free();

            } while (packet.MessageType != MessageType.AcceptJoin && packet.MessageType != MessageType.DenyJoin);
            if (packet.MessageType == MessageType.AcceptJoin)
                return packet.PlayerID;
            return -1;
        }

        /// <summary>
        /// Oczekiwanie na rozpoczęcie gry
        /// </summary>
        /// <returns>Inicjalna lista wiadomości dotycząca obiektów rozmieszczonych w grze</returns>
        public List<ServerStatusResponse> StartGame()
        {
            List<ServerStatusResponse> response = new List<ServerStatusResponse>();
            //oczekiwanie na rozpoczęcie gry
            ServerToClientPacket packet;
            do
            {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);

                byte[] data = udpClient.Receive(ref remote);

                GCHandle pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
                packet =
                    (ServerToClientPacket)
                    Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), typeof(ServerToClientPacket));
                pinnedPacket.Free();
                if (packet.MessageType == MessageType.EndGame)
                {
                    Router.GameState = GameState.End;
                    return response;
                }
            } while (packet.MessageType != MessageType.StartGame);

            //wiadomości inicjalizacyjne
            do
            {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);

                byte[] data = udpClient.Receive(ref remote);

                GCHandle pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
                packet =
                    (ServerToClientPacket)
                    Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), typeof(ServerToClientPacket));
                pinnedPacket.Free();

                if (packet.MessageType == MessageType.Event)
                {
                    response.Add(packet.ServerStatusResponse);
                }
                if (packet.MessageType == MessageType.EndGame)
                {
                    Router.GameState = GameState.End;
                    return response;
                }

            } while (packet.MessageType != MessageType.EndStartGame);

            return response;
        }

        public void SendUpdateRequest(ushort playerId, List<Keys> UserInput)
        {
            ClientToServerPacket packet = new ClientToServerPacket();
            packet.MessageType = MessageType.RequestUpdate;
            packet.PlayerID = playerId;
            packet.KeysPressed = UserInput;
            Send(packet);
        }

        public void SendChatMessage(ushort playerId, string message)
        {
            ClientToServerPacket packet = new ClientToServerPacket() { MessageType = MessageType.ChatText, PlayerID = playerId, Message = message };
            Send(packet);
        }

        public IEnumerable<ServerToClientPacket> Read()
        {
            List<ServerToClientPacket> response = new List<ServerToClientPacket>();
            while (udpClient.Available > 0)
            {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);

                byte[] data = udpClient.Receive(ref remote);

                GCHandle pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
                ServerToClientPacket packet =
                    (ServerToClientPacket)
                    Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), typeof(ServerToClientPacket));
                pinnedPacket.Free();
                response.Add(packet);

            }

            return response;
        }

        public IEnumerable<ServerStatusResponse> GetServerStatusResponses(IEnumerable<ServerToClientPacket> responses)
        {
            List<ServerStatusResponse> result = new List<ServerStatusResponse>();
            foreach (ServerToClientPacket packet in responses)
            {
                if (packet.MessageType == MessageType.EndGame)
                {
                    Router.GameState = GameState.End;
                    return result;
                }
                else if (packet.MessageType != MessageType.Event)
                {
                    result.Add(packet.ServerStatusResponse);
                }
            }
            return result;
        }

        public IEnumerable<ServerStateResponse> GetServerStateResponses(IEnumerable<ServerToClientPacket> responses)
        {
            List<ServerStateResponse> result = new List<ServerStateResponse>();
            foreach (ServerToClientPacket packet in responses)
            {
                if (packet.MessageType == MessageType.Event)
                {
                    ServerStateResponse state = new ServerStateResponse();
                    state.EventType = packet.EventType;
                    state.ObjectId = packet.ServerStatusResponse.playerId;
                    if (state.EventType == EventType.HPUpdate)
                    {
                        state.ComponentIndex = packet.ServerStatusResponse.ComponentIndex;
                        state.HPValue = packet.ServerStatusResponse.HPValue;
                    }
                    else if (packet.EventType == EventType.ChatMessage)
                    {
                        state.Message = packet.ServerStatusResponse.Message;
                    }
                    else
                    {
                        state.ObjectName = packet.ServerStatusResponse.GameObjectName;
                    }
                    state.Position = packet.ServerStatusResponse.Position;
                    state.Rotation = packet.ServerStatusResponse.Rotation;
                    state.Velocity = packet.ServerStatusResponse.Velocity;

                    state.ObjectType = packet.ServerStatusResponse.gameObjectType;
                    result.Add(state);
                }
            }
            return result;
        }

    }
}
